import { Cls } from "../util/Defined";
import { TimeUtil } from "../util/TimeUtil";
import { TypeUtil } from "../util/TypeUtil";
import { ECSCmp } from "./ECSCmp";
import { ECSEntity } from "./ECSEntity";
import { ECSFilter, IECSFilter } from "./ECSFilter";
import { ECSScene } from "./ECSScene";

/**
 * [ECS]系统
 * tip:监听scene或filter事件时,caller设为this在移除时会自动清理监听,否则需要手动清理
 */
export abstract class ECSSys {

    /** 更新顺序 更小则比其他sys更早执行update */
    abstract get updateOrder(): number;
    /** 过滤器参数 */
    abstract get ifilter(): IECSFilter;

    /** 过滤器 */
    filter: ECSFilter;

    constructor(
        /** 所在场景 */
        readonly scene: ECSScene
    ) { }

    /**
     * 遍历过滤器匹配到的实体
     * @param 遍历回调
     */
    eachEntities(each: (entity: ECSEntity) => any) {
        const entities = this.filter.entityCmpsMap.keys();
        for (const entity of entities) {
            each(entity);
        }
    }

    /**
     * 获取匹配到的组件
     * @param cmpType 组件类型
     */
    getCmps<T extends ECSCmp>(cmpType: Cls<T>): Set<T> {
        return this.filter.cmpsMap.get(cmpType) as Set<T>;
    }

    // ============ 生命周期 ============

    /**
     * (虚方法)
     * 当被添加到场景时触发
     */
    onAdd() { }

    /**
     * (虚方法)
     * 当帧循环时(比update前)触发
     */
    onBeforeUpdate() { }

    /**
     * (虚方法)
     * 当帧循环时触发
     */
    onUpdate() { }

    /**
     * (虚方法)
     * 当帧循环时(比update后)触发
     */
    onLateUpdate() { }

    /**
     * (虚方法)
     * 当被移除时触发
     */
    onRemove() { }

    // ============ DEBUG ============

    /** 记录系统运行花费时间 */
    debugRecord: Map<string, {
        /** 执行次数 */
        count: number,
        /** 最近一次花费时间 */
        least: number,
        /** 总计花费时间 */
        total: number,
        /** 平均花费时间 */
        avg: number,
    }>;

    /** 
     * 开启调试 记录系统运行花费时间
     */
    enableDebugRecord() {
        if (this.debugRecord) {
            return;
        }

        this.debugRecord = new Map();
        const prefix = TypeUtil.getCls(this).name + "_";
        const totalRecord = { count: 0, least: 0, total: 0, avg: 0 };
        this.debugRecord.set("total", totalRecord);
        let totalLeast = 0;

        const record = (fn: string) => {
            const timeKey = prefix + fn;
            const record = { count: 0, least: 0, total: 0, avg: 0 };
            this.debugRecord.set(fn, record);

            const oldFn = (this as any)[fn] as Function;
            (this as any)[fn] = () => {
                if (fn === "onBeforeUpdate") {
                    totalLeast = 0;
                }
                TimeUtil.timeStart(timeKey);
                oldFn.call(this);
                ++record.count;
                record.least = TimeUtil.timeEnd(timeKey);
                record.total += record.least;
                record.avg = record.total / record.count;
                totalLeast += record.least;
                if (fn === "onLateUpdate") {
                    ++totalRecord.count;
                    totalRecord.least = totalLeast;
                    totalRecord.total += totalLeast;
                    totalRecord.avg = totalRecord.total / totalRecord.count;
                }
            };
        }

        record("onBeforeUpdate");
        record("onUpdate");
        record("onLateUpdate");
    }

}